using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using RPGProject.GameWorld;
using RPGProject.GameWorld.GameObjects;
using RPGProject.LoopSystem;
using RPGProject;
using RPGProject.IOBuffers;
namespace TileBasedXnaSystem
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private Dictionary<String, Texture2D> textures;
        private Dictionary<String, String> imageTagToImageName;
        private String[][] grid;
        private Vector2 tileSize;
        private int tileWidth;
        private int tileHeight;
        private int xShift;
        private int yShift;
        private SpriteFont courier;
        private World world;
        private LoopManager loopManager;
        private int timeSinceLastLoop;
        private Screen screen;
        /// <summary>
        /// Sets and returns the height of the screen.
        /// </summary>
        public int Height
        {
            get { return this.graphics.PreferredBackBufferHeight; }
            set { this.graphics.PreferredBackBufferHeight = value; }
        }
        /// <summary>
        /// Sets and returns the width of the screen.
        /// </summary>
        public int Width
        {
            get { return this.graphics.PreferredBackBufferWidth; }
            set { this.graphics.PreferredBackBufferWidth = value; }
        }
        /// <summary>
        /// Construct the Game Object.
        /// </summary>
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            this.IsFixedTimeStep = false;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // Tile Based Initialization
            this.IsMouseVisible = true;
            this.tileSize = new Vector2(16, 16);
            this.xShift = 0;
            this.yShift = 0;
            this.textures = new Dictionary<String, Texture2D>();
            this.imageTagToImageName = new Dictionary<String, String>();
            this.grid = new String[this.tileWidth][];
            for (int i = 0; i < this.grid.Count(); i++)
            {
                this.grid[i] = new String[this.tileHeight];
                for (int j = 0; j < this.grid[i].Count(); j++)
                {
                    this.grid[i][j] = "blank";
                }
            }
            // Game Initialization
            this.timeSinceLastLoop = 0;
            this.world = null;
            try
            {
                World.Load(Constants.GetInstance().GetString("StartingMap"), out this.world, 3, 5);
            }
            catch (Exception e)
            {
                List<string> MBOPTIONS = new List<string>();
                MBOPTIONS.Add("OK");
                string msg = e.Message;
                Guide.BeginShowMessageBox(
                        "Error", msg, MBOPTIONS, 0,
                        MessageBoxIcon.Alert, null, null);
                Environment.Exit(0);
                return;
            }
            // Set up constants and the world in general.
            Constants c = Constants.GetInstance();
            this.world["actionPoint"] = c["Player.ActionPoints"];
            this.world.AddStat("actionPoint", new StatOutputBuffer.Stat("Action Points", 0, 0, 20, 2));
            this.world["actionPoint"] = c["Player.ActionPoints"];
            this.world.AddStat("turn", new StatOutputBuffer.Stat("Turn #", 1, 0, 20, 0));
            this.world.AddStat("enemycount", new StatOutputBuffer.Stat("Enemy Count", 1, 21, 20, 0));
            this.world.AddStat("null", new StatOutputBuffer.Stat("Press H for", 2, 21, 20, 0));
            this.world["null"] = "help";
            this.world.Init();
            Player player = (Player)this.world["player"];
            player.AddUpdate(this.world.GetStatUpdate());
            this.world.AddStat("hp", new StatOutputBuffer.Stat("HP", 2, 0, 20, 0));
            this.world.AddStat("name", new StatOutputBuffer.Stat("Player Name", 0, 21, 20, 0));
            player["hp"] = Constants.GetInstance()["Player.MaxHP"];
            player["name"] = "Mirate";

            // Render the world for the first frame
            this.world.Render();
            // Get the LoopManager and flushes its queue
            this.loopManager = this.world.GetLoopManager();
            this.loopManager.Flush();
            // Sets the current screen to render to worlds current screen
            this.screen = this.world.CurrentScreen;
            
            // Resize the screen
            this.Resize();
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            this.textures["blank"] = Content.Load<Texture2D>("tile1");
            this.imageTagToImageName["blank"] = "blank";

            this.textures["empty"] = Content.Load<Texture2D>("blankTile");
            this.imageTagToImageName["DarkYellow"] = "empty";
            this.imageTagToImageName["Gray"] = "empty";

            this.textures["ocean"] = Content.Load<Texture2D>("blueTile");
            this.imageTagToImageName["DarkBlue"] = "ocean";

            this.textures["verticalWall"] = Content.Load<Texture2D>("veticalWall");
            this.imageTagToImageName["Gray|"] = "verticalWall";

            this.textures["horizontalWall"] = Content.Load<Texture2D>("horizontalWall");
            this.imageTagToImageName["Gray-"] = "horizontalWall";

            this.textures["cornerWall"] = Content.Load<Texture2D>("cornerWall");
            this.imageTagToImageName["Gray+"] = "cornerWall";

            this.textures["emptyWall"] = Content.Load<Texture2D>("emptyWall");
            this.imageTagToImageName["Gray."] = "emptyWall";

            this.textures["bulletLeft"] = Content.Load<Texture2D>("bulletLeft");
            this.imageTagToImageName["Green<"] = "bulletLeft";

            this.textures["bulletRight"] = Content.Load<Texture2D>("bulletRight");
            this.imageTagToImageName["Green>"] = "bulletRight";

            this.textures["bulletUp"] = Content.Load<Texture2D>("bulletUp");
            this.imageTagToImageName["Green^"] = "bulletUp";

            this.textures["bulletDown"] = Content.Load<Texture2D>("bulletDown");
            this.imageTagToImageName["Greenv"] = "bulletDown";

            this.textures["bulletUpLeft"] = Content.Load<Texture2D>("bulletUpLeft");
            this.imageTagToImageName["Green\\"] = "bulletUpLeft";

            this.textures["bulletUpRight"] = Content.Load<Texture2D>("bulletUpRight");
            this.imageTagToImageName["Green/"] = "bulletUpRight";

            this.textures["bulletDownLeft"] = Content.Load<Texture2D>("bulletDownLeft");
            this.imageTagToImageName["Green["] = "bulletDownLeft";

            this.textures["bulletDownRight"] = Content.Load<Texture2D>("bulletDownRight");
            this.imageTagToImageName["Green]"] = "bulletDownRight";

            this.textures["pirate"] = Content.Load<Texture2D>("Pirate");
            this.imageTagToImageName["YellowP"] = "pirate";

            this.textures["Ninja100"] = Content.Load<Texture2D>("Ninja100");
            this.imageTagToImageName["CyanN"] = "Ninja100";

            this.textures["Ninja80"] = Content.Load<Texture2D>("Ninja80");
            this.imageTagToImageName["DarkCyanN"] = "Ninja80";

            this.textures["Ninja60"] = Content.Load<Texture2D>("Ninja60");
            this.imageTagToImageName["BlueN"] = "Ninja60";

            this.textures["Ninja40"] = Content.Load<Texture2D>("Ninja40");
            this.imageTagToImageName["DarkBlueN"] = "Ninja40";

            this.textures["Ninja20"] = Content.Load<Texture2D>("Ninja20");
            this.imageTagToImageName["RedN"] = "Ninja20";

            this.textures["Shuriken"] = Content.Load<Texture2D>("Shuriken");
            this.imageTagToImageName["Green+"] = "Shuriken";

            this.textures["white"] = Content.Load<Texture2D>("whiteBackground");
            this.imageTagToImageName["white"] = "white";

            courier = Content.Load<SpriteFont>("Courier");


        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            bool end = false;

            
            // Update keyboard state
            this.loopManager.KeyState = Keyboard.GetState();
            // If the delay is over, run the loop, otherwise just increase the delay counter
            if (this.timeSinceLastLoop >= this.loopManager.CurrentDelay)
            {
                end = !this.loopManager.LoopOnce();
                this.screen = this.world.CurrentScreen;
                this.timeSinceLastLoop = 0;
            }
            else
            {
                this.timeSinceLastLoop += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
            }




            // Scroll properly
            MouseState ms = Mouse.GetState();

            // Check to see if the mouse is in the left and right of the screen, if it is, attempt to scroll.
            int xLeft = (int)(this.Width * 0.3);
            int xRight = (int)(this.Width * 0.7);
            if (ms.X < xLeft && ms.X >= 0)
                this.xShift -= (int)((double)Math.Abs(ms.X - xLeft) / (double)xLeft * (this.Width * 0.01));
            else if (ms.X > xRight && ms.X <= this.Width)
                this.xShift += (int)((double)Math.Abs(ms.X - xRight) / (double)xLeft * (this.Width * 0.01));

            // Check to see if the mouse is in the top and bottom of the screen, if it is, attempt to scroll.
            int yLeft = (int)(this.Height * 0.3);
            int yRight = (int)(this.Height * 0.7);
            if (ms.Y < yLeft && ms.X >= 0)
                this.yShift -= (int)((double)Math.Abs(ms.Y - yLeft) / (double)yLeft * (this.Height * 0.01));
            else if (ms.Y > yRight && ms.Y <= this.Height)
                this.yShift += (int)((double)Math.Abs(ms.Y - yRight) / (double)yLeft * (this.Height * 0.01));

            // Limit the x scrolling
            int diffXSizeAndWindowSize = this.tileWidth * (int)this.tileSize.X - this.Width;
            if (this.xShift < 0)
                this.xShift = 0;
            else if (this.xShift > diffXSizeAndWindowSize)
                this.xShift = diffXSizeAndWindowSize;

            // Limit the y scrolling
            int diffYSizeAndWindowSize = this.tileHeight * (int)this.tileSize.Y - this.Height;
            if (this.yShift < 0)
                this.yShift = 0;
            else if (this.yShift > diffYSizeAndWindowSize)
                this.yShift = diffYSizeAndWindowSize;

            // Exit if the game is over.
            if (end)
                this.Exit();

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            // Resize the window to fit the current screen.
            this.Resize();
            this.spriteBatch.Begin();
            int startX;
            int startY;
            OutputBuffer pictureBuf;
            OutputBuffer textBuf;
            String pic;
            String line;
            // Render the pictures below everything else.
            foreach (Tuple<int, int, OutputBuffer> testAgainst in this.screen.Buffers)
            {
                startX = testAgainst.Item1;
                startY = testAgainst.Item2;
                if (!(testAgainst.Item3 is LogOutputBuffer || testAgainst.Item3 is StatOutputBuffer))
                {
                    pictureBuf = testAgainst.Item3;
                    for (int x = 0; x < pictureBuf.width; x++)
                    {
                        for (int y = 0; y < pictureBuf.height; y++)
                        {
                            pic = pictureBuf[x, y].ToString();
                            // Find the background and foreground and draw them to the tiles location.
                            this.spriteBatch.Draw(this.textures[this.imageTagToImageName[pictureBuf[x, y].background.ToString()]], new Rectangle((int)((x + startX) * this.tileSize.X - this.xShift), (int)((y + startY) * this.tileSize.Y - this.yShift), (int)(this.tileSize.X), (int)(this.tileSize.Y)), Color.White);
                            if (this.imageTagToImageName.ContainsKey(pic))
                                this.spriteBatch.Draw(this.textures[this.imageTagToImageName[pic]], new Rectangle((int)((x + startX) * this.tileSize.X - this.xShift), (int)((y + startY) * this.tileSize.Y - this.yShift), (int)(this.tileSize.X), (int)(this.tileSize.Y)), Color.White);
                            else if(pic != "Black.")
                                this.spriteBatch.Draw(this.textures["blank"], new Rectangle((int)((x + startX) * this.tileSize.X - this.xShift), (int)((y + startY) * this.tileSize.Y - this.yShift), (int)(this.tileSize.X), (int)(this.tileSize.Y)), Color.White);
                            
                        }
                    }
                }
            }
            // Render the text on top.
            foreach (Tuple<int, int, OutputBuffer> testAgainst in this.screen.Buffers)
            {
                startX = (int)(testAgainst.Item1 * this.tileSize.X);
                startY = (int)(testAgainst.Item2 * this.tileSize.Y);
                if (testAgainst.Item3 is LogOutputBuffer || testAgainst.Item3 is StatOutputBuffer)
                {
                    textBuf = testAgainst.Item3;
                    // Clear the space where the line exists.
                    this.spriteBatch.Draw(this.textures["white"], new Rectangle(startX, startY, (int)(textBuf.width * this.tileSize.X), (int)(textBuf.height * this.tileSize.Y)), Color.White);

                    for (int y = 0; y < textBuf.height; y++)
                    {
                        line = "";
                        for (int x = 0; x < textBuf.width; x++)
                        {
                            line += textBuf[x, y].c;
                        }
                        this.spriteBatch.DrawString(this.courier, line, new Vector2(startX, startY + (y) * this.tileSize.Y), Color.Black);

                    }
                }
            }

            //this.spriteBatch.DrawString(this.courier, this.timeSinceLastLoop.ToString(), new Vector2(400, 0), Color.Black);

            this.spriteBatch.End();

            base.Draw(gameTime);
        }
        /// <summary>
        /// Resize the window to fit the current screen.
        /// </summary>
        protected void Resize()
        {
            this.tileWidth = this.screen.Width;
            this.tileHeight = this.screen.Height;
            this.Width = (int)Math.Min(this.tileWidth * this.tileSize.X, 800);
            this.Height = (int)Math.Min(this.tileHeight * this.tileSize.Y, 600);
            this.graphics.ApplyChanges();

        }
    }
}
